/**
 * @Description:
 * @Author BPF
 * @create 2021/11/26 9:25
 */
import dayjs from 'dayjs'
import { Type, MathUtils } from '@bianpengfei/utils'
import { handlerDay, handlerMonth, handlerQuarter, handlerYear } from './statistical-comparison'

const { Decimal } = MathUtils

export /**
 * 格式化label文字，换行
 * @param label 文字
 * @param len 按照每隔len个文字换行
 * @returns {string|*}
 */
const formatAxisLabel = (label, len = 6) => {
  if (label.length > len) {
    let newParamsName = ''
    let paramsNameNumber = label.length
    let provideNumber = len
    let rowNumber = Math.ceil(paramsNameNumber / provideNumber)
    if (paramsNameNumber > provideNumber) {
      for (let p = 0; p < rowNumber; p++) {
        let tempStr = ''
        let start = p * provideNumber
        let end = start + provideNumber
        if (p == rowNumber - 1) {
          tempStr = label.substring(start, paramsNameNumber)
        } else {
          tempStr = label.substring(start, end) + '\n'
        }
        newParamsName += tempStr
      }
    } else {
      newParamsName = label
    }
    return newParamsName
  }
  return label
}

/**
 * 日期相减
 * @param date
 * @param value
 * @param unit
 * @param template
 * @returns {string}
 */
export const subDate = (date, value, unit = 'day', template = 'YYYY-MM-DD') =>
  dayjs(date).subtract(value, unit).format(template)

/**
 * 日期相加
 * @param date
 * @param value
 * @param unit
 * @param template
 * @returns {string}
 */
export const addDate = (date, value, unit = 'day', template = 'YYYY-MM-DD') =>
  dayjs(date).add(value, unit).format(template)

/**
 * 计算相对时间，返回 业务上 日/月/季/年中 开始时间 和 结束时间, 上次开始时间 上次结束时间
 * @param dayUnit
 * @param curToday
 * @returns {{start: string, end: string}}
 */
export const computedDays = (dayUnit = '日', curToday = dayjs().format('YYYY-MM-DD')) => {
  let start = '',
    end = '',
    lastStart = '', // 上次开始时间，用于和上次比较
    lastEnd = '',
    weekStart = '', //  一周前日期
    twoWeekStart = '', // 两周前
    chainMonthStart = '', // 月环比开始
    chainMonthEnd = '', // 月环比结束
    list = [] // 日期区间

  const map = new Map([
    [
      '日',
      () => {
        start = subDate(curToday, 1, 'day')
        end = curToday
        lastStart = subDate(start, 1, 'day')
        lastEnd = start
        weekStart = subDate(curToday, 7, 'day')
        twoWeekStart = subDate(curToday, 14, 'day')
        list = [...new Array(7)].map((v, vi) => subDate(start, vi, 'day')).reverse()
      }
    ],
    [
      '月',
      () => {
        const year = curToday.toString().slice(0, 4)
        const month = curToday.toString().slice(5, 7)
        const daysInMonth = dayjs(curToday).daysInMonth()
        start = `${year}-${month}-01`
        end = `${addDate(start, 1, 'month', 'YYYY-MM')}-01`
        lastStart = `${subDate(start, 1, 'month', 'YYYY-MM')}-01`
        lastEnd = start
        list = [...new Array(daysInMonth)].map((v, vi) => addDate(start, vi, 'day'))
        chainMonthStart = subDate(start, 1, 'year')
        chainMonthEnd = subDate(end, 1, 'year')
      }
    ],
    [
      '季',
      () => {
        let year = curToday.toString().slice(0, 4)
        const quarter = {
          1: [1, 2, 3],
          2: [4, 5, 6],
          3: [7, 8, 9],
          4: [10, 11, 12]
        }
        let month = curToday.toString().slice(5, 7)
        let quarterMonths = []
        // let quarterNum = null
        // eslint-disable-next-line no-unused-vars
        Object.entries(quarter).some(([key, value]) => {
          if (Array.isArray(value) && value.includes(Number(month))) {
            quarterMonths = value
            // quarterNum = key
            return true
          }
        })
        start = `${year}-${quarterMonths[0].toString().padStart(2, '0')}-01`
        end = `${addDate(start, 3, 'month', 'YYYY-MM')}-01`
        chainMonthStart = subDate(start, 1, 'year')
        chainMonthEnd = subDate(end, 1, 'year')
        lastStart = `${subDate(start, 3, 'month', 'YYYY-MM')}-01`
        lastEnd = start
        list = quarterMonths.map(v => v.toString().padStart(2, '0'))
      }
    ],
    [
      '年',
      () => {
        let year = curToday.toString().slice(0, 4)
        start = `${year}-01-01`
        end = `${addDate(start, 1, 'year', 'YYYY-MM')}-01`
        lastStart = `${subDate(start, 1, 'year', 'YYYY-MM')}-01`
        lastEnd = start
        list = [...new Array(new Date().getMonth() + 1)].map((v, vi) => (vi + 1).toString().padStart(2, '0'))
      }
    ]
  ])
  map.get(dayUnit)?.()
  return {
    lastStart, // 上次开始时间  如果是日：是前天  如果是月：上个月月初  如果是年：去年年初
    lastEnd,
    start,
    end,
    weekStart,
    twoWeekStart,
    list,
    chainMonthStart,
    chainMonthEnd
  }
}

/**
 * 自动化注册 需要模块
 * @param requireContext 语法require.context(目录,是否递归目录, 文件筛选条件)所有参数必须是直接字符串，变量无效。
 * @param exclude 只引入包括的模块
 * @param include 排除的模块 include 和 exclude不能同时共用
 * @returns {*}  Array
 */
export const requireAllModule = ({ requireContext, exclude = [], include = [] }) => {
  if (!Array.isArray(exclude)) {
    console.error('[warning] exclude字段类型应为Array')
    exclude = []
  }
  if (!Array.isArray(include)) {
    console.error('[warning] include字段类型应为Array')
    include = []
  }
  const isNoEmptyArray = function () {
    return !!(Array.isArray(this) && this.length)
  }
  const isEmptyArray = function () {
    return !!(Array.isArray(this) && this.length === 0)
  }
  if (isNoEmptyArray.apply(exclude) && isNoEmptyArray.apply(include)) {
    throw new Error('include 和 exclude字段不能同时共用')
  }
  const requireAll = context =>
    context.keys().reduce((pre, cur) => {
      const _key = cur.replace(/^\.\/(.*)\.\w+$/, '$1')
      if (isNoEmptyArray.apply(include)) {
        include.some(v => new RegExp(v).test(_key)) && pre.push(context(cur))
      } else if (isNoEmptyArray.apply(exclude)) {
        exclude.every(v => !new RegExp(v).test(_key)) && pre.push(context(cur))
      } else if (isEmptyArray.apply(include) && isEmptyArray.apply(exclude)) {
        pre.push(context(cur))
      }
      return pre
    }, [])
  return requireAll(requireContext)
}

/**
 * 截流函数
 * @param fn
 * @param wait
 */
export function throttle(fn, wait = 800) {
  let startTime = 0
  return function () {
    let now = Date.now()
    let resTime = now - startTime
    if (resTime >= wait) {
      fn.apply(this, arguments)
      startTime = now
    }
  }
}

/**
 * 通过在列表中 自动计算出 各个字段总和，各个字段所占比例
 * @param {array} list  列表
 * @param {string} primaryKey 主键，用于过滤重复
 * @param {array} fields 需要字段计算总和, 不传默认是取所有整数类型字段 总和 __TOTAL_  比例 __RATE_
 * @param {array} divisionFields 一个字段占另一个字段值占比,每行内部相除，
 * @param {string | array} orderBy 按照哪个字段进行排序
 * @param {string} orderByDirection 升序（从小到大） ASC   降序（从大到小） DESC
 * @returns {{fieldsObj: {}, list: *[], fields: *[]}}
 */
export const calSummaryList = ({
  list = [],
  primaryKey = 'KS_ID',
  fields = [],
  divisionFields = [],
  orderBy = [],
  orderByDirection = 'DESC'
}) => {
  // 常量
  const CONSTANT = {
    __TOTAL_: '__TOTAL_', // 总和前缀
    __RATE_: '__RATE_', // 某一字段占总和比 前缀
    __DIV_: '__DIV_' // 行内一个字段占另一个字段值占比
  }

  // 拷贝list
  const _listClone = JSON.parse(JSON.stringify(list))

  // 去重获取唯一主键的值数组
  const primaryKeyValues = [...new Set(_listClone.map(v => v[primaryKey]).filter(v => !!v))]

  // 根据唯一主键数组过滤，并累加各个字段的值（只有验证字段值数据类型是数字和field里面字段的，才可以累加）
  const _list = primaryKeyValues
    .map(v => {
      const listByPK = _listClone.filter(o => o[primaryKey] == v)
      if (listByPK.length) {
        // 从列表中读取第一个对象，并遍历该对象，如果数据类型是数字型或者是fields指定的字段，设置0
        const [item0 = {}] = listByPK
        const item0Clone = JSON.parse(JSON.stringify(item0))
        const keysMap = [...Object.keys(item0Clone), ...fields]
        keysMap.forEach(key => {
          // 判断是键值是否是数字型或fields存在key且值是数字型
          const flag = Type.isNumber(item0Clone[key]) || fields.includes(key)
          if (flag) item0Clone[key] = 0
        })
        fields.forEach(key => !item0[key] && (item0[key] = 0))
        return listByPK.reduce((pre, cur) => {
          keysMap.forEach(k => {
            if (!isNaN(cur[k]) && Type.isNumber(cur[k])) {
              if (!pre[k]) pre[k] = 0
              pre[k] = +new Decimal(+pre[k]).add(+cur[k]).toFixed()
              // pre[k] = Number(pre[k]) + Number(cur[k])
            }
          })
          return pre
        }, item0Clone)
      }
      return []
    })
    .flat(Infinity)

  // 转化fields转成对象
  let fieldsObj = {}
  if (fields?.length) {
    fieldsObj = fields.reduce((pre, cur) => {
      cur && (pre[cur] = 0)
      return pre
    }, {})
  } else {
    const [item0 = {}] = _list
    fieldsObj = Object.entries(item0).reduce((pre, cur) => {
      const [key, value] = cur
      if (Type.isNumber(value)) {
        pre[key] = 0
      }
      return pre
    }, {})
    fields = Object.keys(fieldsObj)
  }

  // 计算出field对象中各个字段在list中总和
  fieldsObj = _list.reduce((pre, cur) => {
    fields.forEach(key => {
      if (cur[key]) {
        pre[key] = +new Decimal(+pre[key]).add(+cur[key]).toFixed()
      }
    })
    return pre
  }, fieldsObj)

  // 返回list，在原有的list中分别添加，字段总和 和 字段值占比, 以及一个字段占另一个字段值占比,每行内部相除，
  const _ResultList = _list.map(v => {
    const resultObj = {}
    Object.entries(fieldsObj).forEach(([key, value]) => {
      resultObj[`${CONSTANT.__TOTAL_}${key}`] = value
      resultObj[`${CONSTANT.__RATE_}${key}`] = v[key] && value ? +new Decimal(+v[key]).div(+value).toFixed(4) : 0 // TODO
    })
    if (divisionFields.length) {
      divisionFields.forEach(([sourceField, targetField]) => {
        resultObj[`${CONSTANT.__DIV_}${sourceField}_${targetField}`] = +v[targetField]
          ? +new Decimal(v[sourceField]).div(+v[targetField]).toFixed(4)
          : 0
      })
    }
    return {
      ...v,
      ...resultObj
    }
  })

  // 排序， 根据orderBy数组字段进行排序，如果第一个字段值相同，那么比较第二个字段，依次类推，DESC从大到小  ASC从小到大
  if (orderBy) {
    if (Array.isArray(orderBy) && orderBy?.length) {
      // 如果orderBy是数组
      const sortFn = (a, b, orderBy = []) => {
        if (!orderBy[0]) {
          return 0
        }
        if (!isNaN(a[orderBy[0]]) && !isNaN(b[orderBy[0]]) && +a[orderBy[0]] == +b[orderBy[0]]) {
          return sortFn(a, b, orderBy.slice(1))
        }
        return orderByDirection.toUpperCase() == 'ASC'
          ? +a[orderBy[0]] > +b[orderBy[0]]
            ? 1
            : -1
          : +b[orderBy[0]] > +a[orderBy[0]]
          ? 1
          : -1
      }
      _ResultList.sort((a, b) => sortFn(a, b, orderBy))
    } else if (Type.isString(orderBy)) {
      // 如果orderBy是字符串
      _ResultList.sort((a, b) => {
        if (!isNaN(a[orderBy]) && !isNaN(b[orderBy]) && +a[orderBy] == +b[orderBy]) {
          return 0
        }
        return orderByDirection.toUpperCase() == 'ASC'
          ? +a[orderBy] > +b[orderBy]
            ? 1
            : -1
          : +b[orderBy] > +a[orderBy]
          ? 1
          : -1
      })
    }
  }

  // 返回field中字段 累加总和，并包裹以对象的方式返回
  fieldsObj = Object.entries(fieldsObj).reduce((pre, cur) => {
    const [key, value] = cur
    pre[`${CONSTANT.__TOTAL_}${key}`] = value
    return pre
  }, {})

  return {
    originList: list,
    list: _ResultList,
    fieldsObj: fieldsObj,
    fields,
    ...CONSTANT
  }
}

/**
 * 格式化数字
 * @param num 数字
 * @param fixedLen 保留数字小数长度
 * @param force  true 如果动态的根据值，值不超过1w，就返回整个数值，不需要格式化   false：强行格式化成w，
 * @param Type 格式化单位， w：万  k：千 如果是w，小于100
 * @returns {*}
 */
export const formatNum = (num = 0, fixedLen = -1, force = false, Type = 'W') => {
  if (isNaN(num) || num === '0') num = 0
  const wFn = (num = 0) => {
    if (!force && +num < 10 ** 4) return fixedLen == -1 ? +num : +num.toFixed(fixedLen)
    const decNum = new Decimal(+num).div(10 ** 4)
    const decNumFixed = fixedLen == -1 ? +decNum.toFixed() : +decNum.toFixed(fixedLen)
    return decNumFixed == 0 ? 0 : `${decNumFixed}w`
  }
  const kFn = (num = 0) => {
    if (!force && +num < 10 ** 3) return fixedLen == -1 ? +num : +num.toFixed(fixedLen)
    const decNum = new Decimal(+num).div(10 ** 3)
    const decNumFixed = fixedLen == -1 ? `${decNum.toFixed()}k` : `${decNum.toFixed(fixedLen)}k`
    return decNumFixed == 0 ? 0 : `${decNumFixed}k`
  }
  const conf = {
    W: wFn,
    K: kFn
  }
  return conf[Type.toString().toUpperCase()]?.(num) || num
}

/**
 * 计算比率，得出占比、上升/下降了多少比率
 * @param data
 */
export const calRate = (data = { cur: 0, last: 0 }) => {
  const diffDecimal = new Decimal(+data.cur).sub(+data.last)
  const compareRateNum = data.cur ? diffDecimal.abs().div(+data.cur) : 0
  return {
    cur: data.cur,
    last: data.last,
    trend: +diffDecimal.toFixed() > 0 ? 'up' : +diffDecimal.toFixed() < 0 ? 'down' : '',
    compareRate: `${compareRateNum.mul(100).toFixed(2)}%`,
    compareRateNum: +compareRateNum.toFixed(4),
    compareNum: +diffDecimal.toFixed()
  }
}

/**
 * 封装常用的 计算总和、平均值
 * @Type {{sum: (function(*=): *)}}
 */
export const mathUtils = {
  sum: (...args) => args.reduce((p, c) => +new Decimal(p).add(c).toFixed(), 0),
  avg: function (...args) {
    return +new Decimal(this.sum(...args)).div(args.length).toFixed()
  },
  div: (arg1, arg2) => {
    return isNaN(arg2) || +arg2 == 0 ? 0 : +new Decimal(+arg1 || 0).div(+arg2).toFixed()
  },
  mul: (arg1, arg2) => +new Decimal(+arg1 || 0).mul(+arg2 || 0).toFixed()
}

/**
 * 统计对比
 * @param list
 * @param params
 * @param timeType
 * @returns {*}
 */
export const calStatisticalList = ({ list = [], params = {}, timeType = '日' }) => {
  // 常量
  const CONSTANT = {
    __TOTAL_: '__TOTAL_', // 总和前缀
    __PRE_DIFF_RATE_: '__PRE_DIFF_RATE_', // 同比， 下降 或 上升比率
    __PRE_DIFF_RATE_LABEL_: '__PRE_DIFF_RATE_LABEL_', // 同比， 下降 或 上升比率，保留两位小数百分比
    __PRE_TREND_: '__PRE_TREND_', // 趋势 是下降 down 还是上升 up
    __PRE_DIFF_: '__PRE_DIFF_', // 当前与上次比较 相差值

    __CHAIN_DIFF_RATE_: '__CHAIN_DIFF_RATE_', // 环比同比， 下降 或 上升比率
    __CHAIN_DIFF_RATE_LABEL_: '__CHAIN_DIFF_RATE_LABEL_', // 环比同比， 下降 或 上升比率， 保留两位小数百分比
    __CHAIN_TREND_: '__CHAIN_TREND_', // 环比趋势 是下降 down 还是上升 up
    __CHAIN_DIFF_: '__CHAIN_DIFF_' // 当前与环比比较 相差值
  }
  const _listClone = JSON.parse(JSON.stringify(list))
  let _list = _listClone.map(v => {
    return {
      ...v,
      date: v.date || (v.D_ID ? dayjs(v.D_ID).format('YYYY-MM-DD') : '')
    }
  })

  // fix key值重复的问题
  const callList = calSummaryList({
    list: _list,
    primaryKey: 'date'
  })
  let _listObj = callList.list
    .map(v => {
      if (Type.isObject(v)) {
        const fieldMap = Object.keys(v).filter(key => {
          return !String(key).startsWith('__')
        })
        return fieldMap.reduce((pre, cur) => {
          pre[cur] = v[cur]
          return pre
        }, {})
      }
      return v
    })
    .reduce((pre, cur) => {
      if (cur?.date) pre[cur?.date] = cur
      return pre
    }, {})

  const confFn = {
    日: handlerDay.bind(null, _list, _listObj, params),
    月: handlerMonth.bind(null, _list, _listObj, params),
    季: handlerQuarter.bind(null, _list, _listObj, params),
    年: handlerYear.bind(null, _list, _listObj, params)
  }
  if (!confFn?.[timeType]) {
    throw new Error('传入的参数 timeType 期望是 年、季、月、日一种')
  }
  const result = confFn?.[timeType]?.()
  const { summary, summaryPre, xAxisSummaryChain } = result
  Object.keys(summary).forEach(key => {
    if (!isNaN(summary[key]) && Type.isNumber(summary[key])) {
      const _key = key.replace(CONSTANT.__TOTAL_, '')
      const { trend, compareRateNum, compareNum, compareRate } = calRate({
        cur: summary[key] || 0,
        last: summaryPre[key] || 0
      })
      summary[`${CONSTANT.__PRE_TREND_}${_key}`] = trend
      summary[`${CONSTANT.__PRE_DIFF_RATE_}${_key}`] = compareRateNum || 0
      summary[`${CONSTANT.__PRE_DIFF_RATE_LABEL_}${_key}`] = compareRate
      summary[`${CONSTANT.__PRE_DIFF_}${_key}`] = compareNum || 0
      if (xAxisSummaryChain) {
        const { trend, compareRateNum, compareNum, compareRate } = calRate({
          cur: summary[key] || 0,
          last: xAxisSummaryChain[key] || 0
        })
        summary[`${CONSTANT.__CHAIN_TREND_}${_key}`] = trend
        summary[`${CONSTANT.__CHAIN_DIFF_RATE_}${_key}`] = compareRateNum || 0
        summary[`${CONSTANT.__CHAIN_DIFF_RATE_LABEL_}${_key}`] = compareRate
        summary[`${CONSTANT.__CHAIN_DIFF_}${_key}`] = compareNum || 0
      }
    }
  })
  result.originList = list
  return result
}
